package com.iakuil.app.common.redis;

import net.javacrumbs.shedlock.core.LockProvider;
import net.javacrumbs.shedlock.provider.redis.spring.RedisLockProvider;
import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;

/**
 * Redis分布式锁和限流脚本自动配置
 *
 * @author Kai
 */
@AutoConfiguration
@ConditionalOnClass(RedisAutoConfiguration.class)
@EnableSchedulerLock(defaultLockAtMostFor = "PT30S")
public class RedisLockAndLimiterAutoConfiguration {

    @Value("${spring.profiles.active}")
    private String env;

    @Bean
    @ConditionalOnMissingBean
    public LockProvider lockProvider(RedisConnectionFactory redisConnectionFactory) {
        return new RedisLockProvider(redisConnectionFactory, env);
    }

    /**
     * Redis Lua限流脚本
     *
     * <p>简单计数，请勿用于高并发场景。
     * <p>参考：<a href="https://www.cnblogs.com/zhangxinhua/p/14893185.html">基于redis实现的四种常见的限流策略</a>
     */
    @Bean
    @ConditionalOnMissingBean
    public RedisScript<Long> limitRateScript() {
        String lua = "local c" +
            "\nc = redis.call('get',KEYS[1])" +
            // 调用超过最大值，则直接返回
            "\nif c and tonumber(c) > tonumber(ARGV[1]) then" +
            "\nreturn c;" +
            "\nend" +
            // 执行计算器自加
            "\nc = redis.call('incr',KEYS[1])" +
            "\nif tonumber(c) == 1 then" +
            // 从第一次调用开始限流，设置对应键值的过期
            "\nredis.call('expire',KEYS[1],ARGV[2])" +
            "\nend" +
            "\nreturn c;";
        return new DefaultRedisScript<>(lua, Long.class);
    }
}
